package socketmvc.core.packets.basic.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import socketmvc.core.packets.basic.IFilePacket;
import socketmvc.core.packets.basic.IMultiFilePacket;
import socketmvc.core.packets.basic.JsonBasicPacket;
import socketmvc.core.packets.basic.Protocol;
import socketmvc.core.packets.biz.SockFile;
import socketmvc.core.packets.biz.SockMultiFile;
import java.io.Serial;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class JsonBinPacket extends JsonBasicPacket implements IFilePacket, IMultiFilePacket, Serializable {

    @Serial
    private static final long serialVersionUID = -8655453456888L;

    // 规定当该key取值是 0的时候代表是单文件上传，1的时候代表多文件上传
    final static String FILE_TYPE_KEY = "@file-type";

    protected SockFile file;
    protected SockMultiFile multiFile;

    public JsonBinPacket(JsonTextBody textBody, byte[] binBody) {
        if (textBody != null){
            Integer fileType = textBody.getHeaders().getInteger(FILE_TYPE_KEY);
            if (fileType != null){
                this.textBody = textBody;
                this.binBody = binBody;
            }else{
                construct(textBody,new SockFile(binBody,"Unknown"));
            }
        }else{
            this.binBody = binBody;
        }
    }

    public JsonBinPacket(JsonTextBody textBody, SockMultiFile files) {
        construct(textBody,files);
    }

    public JsonBinPacket(JsonTextBody textBody, SockFile file) {
        construct(textBody,file);
    }

    private void construct(JsonTextBody textBody, SockFile file){
        this.textBody = textBody;
        this.file = file;
        this.binBody = file.getBytes();
        JsonTextBody textBodyAsObj = this.getTextBodyAsObj();
        textBodyAsObj.setHeader(FILENAME_KEY, file.getFileName());
        textBodyAsObj.setHeader(FILE_TYPE_KEY, 0);
        if (file.isPart()){
            JSONObject part = new JSONObject();
            part.put("part",file.isPart());
            part.put("total",file.getTotalLength());
            part.put("start",file.getStartIndex());
            part.put("end",file.getEndIndex());
            textBodyAsObj.setHeader(FILE_PART_KEY, part);
        }
    }

    private void construct(JsonTextBody textBody, SockMultiFile files){
        this.textBody = textBody;
        int totalLen = files.getTotalSizeOfFiles(null);
        ByteBuffer buffer = ByteBuffer.allocate(totalLen);
        JSONArray fileNames = new JSONArray();
        JSONArray fileLengths = new JSONArray();
        JSONArray fileParts = new JSONArray();
        for (SockFile sockFile : files) {
            fileNames.add(sockFile.getFileName());
            fileLengths.add(sockFile.getSize());
            JSONObject part = new JSONObject();
            part.put("part",sockFile.isPart());
            if (sockFile.isPart()){
                part.put("total",sockFile.getTotalLength());
                part.put("start",sockFile.getStartIndex());
                part.put("end",sockFile.getEndIndex());
            }
            fileParts.add(part);
            buffer.put(sockFile.getBytes());
        }
        this.binBody = buffer.array();
        this.multiFile = files;

        JsonTextBody textBodyAsObj = this.getTextBodyAsObj();
        textBodyAsObj.setHeader(MULTI_FILENAMES_KEY, fileNames);
        textBodyAsObj.setHeader(MULTI_FILE_LENS_KEY, fileLengths);
        textBodyAsObj.setHeader(MULTI_FILE_PARTS_KEY, fileParts);
        textBodyAsObj.setHeader(FILE_TYPE_KEY, 1);
    }


    @Override
    public byte protocolVersion() {
        return Protocol.JSON_TEXT_BIN_CONTENT.getVersion();
    }

    @Override
    public String protocolName() {
        return Protocol.JSON_TEXT_BIN_CONTENT.getName();
    }

    @Override
    public SockFile toFile() {
        JSONObject headers = this.getTextBodyAsObj().getHeaders();
        Integer fileType = headers.getInteger(FILE_TYPE_KEY);
        if (fileType == null){
            return file;
        }
        if (fileType == 0){
            //  0 代表按照单文件解析方式取二进制内容解析成文件
            if (file != null){
                return file;
            }
            byte[] binBody = getFileBinary();
            if (binBody != null && binBody.length > 0){
                file = new SockFile(binBody, getFilename());
                // 获取文件的零件标识,如果是零件则恢复零件的一些必要属性
                JSONObject part = headers.getJSONObject(FILE_PART_KEY);
                if (part != null && part.getBooleanValue("part")){
                    file.part(part.getInteger("total"),part.getInteger("start"),part.getInteger("end"));
                }
            }
        }else {
            // 多文件解析方式
            SockMultiFile multiFile = this.toMultiFile();
            if (multiFile != null && !multiFile.isEmpty()) {
                file = multiFile.getFile(0);
            }
        }
        return file;
    }

    @Override
    public SockMultiFile toMultiFile() {
        if (multiFile == null) {
            JSONObject headers = this.getTextBodyAsObj().getHeaders();
            JSONArray fileNames = headers.getJSONArray(MULTI_FILENAMES_KEY);
            if (fileNames == null || fileNames.isEmpty()) {
                return null;
            }
            byte[] binBody = this.getBinBodyAsBytes();
            if (binBody == null || binBody.length ==0){
                return null;
            }
            JSONArray fileLengths = headers.getJSONArray(MULTI_FILE_LENS_KEY);
            JSONArray fileParts = headers.getJSONArray(MULTI_FILE_PARTS_KEY);

            int pos = 0;
            List<SockFile> files = new ArrayList<>();
            for (int i = 0; i < fileLengths.size(); i++) {
                String name = fileNames.getString(i);
                Integer len = fileLengths.getInteger(i);
                JSONObject part = fileParts.getJSONObject(i);
                byte[] fileBytes = new byte[len];
                System.arraycopy(binBody, pos, fileBytes, 0, len);
                SockFile sockFile = new SockFile(fileBytes, name);
                if (part.getBooleanValue("part")){
                    sockFile.part(part.getInteger("total"),part.getInteger("start"),part.getInteger("end"));
                }
                files.add(sockFile);
                pos = pos + len ;
            }
            multiFile = new SockMultiFile(files);
        }
        return multiFile;
    }

    @Override
    public Map<String, Object> getFileBinaryDesc() {
        return getTextBodyAsObj().getHeaders();
    }

    @Override
    public byte[] getFileBinary() {
        return getBinBodyAsBytes();
    }

}
